Select a Station
\...
\\
{\rtf1\ansi\ansicpg1252\cocoartf2580 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fmodern\fcharset0 Courier;\f1\froman\fcharset0 Times-Roman;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid1\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid1}} {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} \margl1440\margr1440\vieww11520\viewh8400\viewkind0 \deftab720 \pard\pardeftab720\partightenfactor0 \f0\fs24 \cf2 \expnd0\expndtw0\kerning0 \outl0\strokewidth0 \strokec2 ';\ \}\ add_shortcode( 'tracyradio_carplay', 'tracyradio_carplay_shortcode_handler' );\ \ // 2. Enqueue script and styles, and pass data to the script\ function tracyradio_enqueue_scripts() \{\ // We only want to load these on pages where the shortcode is present.\ // This is a simple check; more complex sites might need a more robust solution.\ if ( is_singular() && has_shortcode( get_post()->post_content, 'tracyradio_carplay' ) ) \{\ \ // Register the script but don't enqueue it yet.\ wp_register_script(\ 'tracyradio-player',\ false, // We will add the script inline.\ [],\ '25.0629.1',\ true // Load in the footer.\ );\ \ // Add the main JavaScript logic inline.\ wp_add_inline_script( 'tracyradio-player', tracyradio_get_player_javascript() );\ \ // Localize script to pass PHP variables to JavaScript.\ // This is the correct way to pass the AJAX URL and nonce.\ wp_localize_script( 'tracyradio-player', 'tracyRadioData', [\ 'ajax_url' => admin_url( 'admin-ajax.php' ),\ 'nonce' => wp_create_nonce( 'tracyradio-metadata-nonce' ),\ ]);\ \ // Now enqueue the script.\ wp_enqueue_script( 'tracyradio-player' );\ \ // Add styles inline.\ wp_add_inline_style( 'tracyradio-player', tracyradio_get_player_css() );\ \}\ \}\ add_action( 'wp_enqueue_scripts', 'tracyradio_enqueue_scripts' );\ \ \ // 3. The server-side proxy for fetching metadata securely\ function tracyradio_get_metadata_ajax_handler() \{\ // Verify the request to prevent misuse.\ check_ajax_referer( 'tracyradio-metadata-nonce', '_nonce' );\ \ // Get the URL from the AJAX request.\ $url = isset( $_POST['url'] ) ? esc_url_raw( $_POST['url'] ) : '';\ \ if ( empty( $url ) ) \{\ wp_send_json_error( 'URL is missing.' );\ return;\ \}\ \ // Use WordPress's built-in HTTP functions to make the request.\ $response = wp_remote_get( $url, [\ 'timeout' => 10,\ 'headers' => [ 'Accept' => 'application/json' ],\ ]);\ \ if ( is_wp_error( $response ) ) \{\ wp_send_json_error( $response->get_error_message() );\ return;\ \}\ \ $body = wp_remote_retrieve_body( $response );\ $data = json_decode( $body, true );\ \ // Check if JSON decoding was successful.\ if ( json_last_error() !== JSON_ERROR_NONE ) \{\ // Sometimes the body might not be perfect JSON, so we send the raw text as a fallback.\ wp_send_json_success( ['songtitle' => $body] );\ return;\ \}\ \ wp_send_json_success( $data );\ \}\ // Hook for both logged-in and non-logged-in users.\ add_action( 'wp_ajax_tracyradio_get_metadata', 'tracyradio_get_metadata_ajax_handler' );\ add_action( 'wp_ajax_nopriv_tracyradio_get_metadata', 'tracyradio_get_metadata_ajax_handler' );\ \ \ // 4. Function to return the CSS for the player.\ function tracyradio_get_player_css() \{\ return "\ /* Basic reset for full-screen player */\ html, body \{ height: 100%; margin: 0; padding: 0; overflow: hidden; background-color: #000; \}\ #tracyradio-player-container \{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 99999; \}\ body \{ font-family: 'Inter', sans-serif; -webkit-tap-highlight-color: transparent; \}\ #app-container \{ background-color: transparent; z-index: 10; \}\ #background-artwork \{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; background-size: cover; background-position: center; filter: blur(25px) brightness(0.6); transition: opacity 0.6s ease-in-out; opacity: 1; transform: scale(1.1); \}\ #visualizer-canvas \{ position: fixed; left: 0; bottom: 0; width: 100%; height: 100%; z-index: 0; opacity: 0.5; transition: opacity 0.5s ease-in-out; \}\ .control-btn:focus-visible \{ outline: 3px solid #85ad85; outline-offset: 2px; border-radius: 9999px; \}\ h1, h2, p \{ text-shadow: 2px 2px 5px rgba(0,0,0,0.7); \}\ #station-logo \{ filter: drop-shadow(0px 4px 6px rgba(0,0,0,0.6)); max-height: 100%; max-width: 80%; width: auto; object-fit: contain; transition: opacity 0.5s ease-in-out; \}\ #station-display \{ height: 100px; display: flex; align-items: center; justify-content: center; \}\ #other-stations-preview \{ backdrop-filter: blur(10px); background-color: rgba(255,255,255,0.05); -webkit-backdrop-filter: blur(10px); overflow: hidden; \}\ #station-preview-container \{ overflow-x: auto; -webkit-overflow-scrolling: touch; \}\ #station-preview-container::-webkit-scrollbar \{ display: none; \}\ #station-preview-container \{ -ms-overflow-style: none; scrollbar-width: none; \}\ .station-preview-item \{ cursor: pointer; transition: transform 0.2s ease-in-out; flex-shrink: 0; \}\ .station-preview-item:hover \{ transform: scale(1.1); \}\ .station-preview-img \{ width: 56px; height: 56px; border: 2px solid rgba(255,255,255,0.4); transition: border-color 0.3s, box-shadow 0.3s; \}\ .station-preview-img.glowing \{ border-color: #669966; box-shadow: 0 0 15px #669966; \}\ #playing-channel-name \{ transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out; transform: translateY(-10px); opacity: 0; height: 0; \}\ #playing-channel-name.visible \{ transform: translateY(0); opacity: 1; height: auto; margin-bottom: 0.25rem; \}\ .modal-overlay \{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 50; display: none; align-items: center; justify-content: center; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(5px); opacity: 0; transition: opacity 0.3s ease-in-out; \}\ .modal-overlay.visible \{ display: flex; opacity: 1; \}\ .modal-content \{ background-color: #1f2937; border-radius: 1rem; width: 90%; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); transform: scale(0.95); transition: transform 0.3s ease-in-out; display: flex; flex-direction: column; overflow: hidden; \}\ .modal-overlay.visible .modal-content \{ transform: scale(1); \}\ #whats-playing-modal .modal-content \{ max-width: 400px; max-height: 80vh; \}\ #request-modal .modal-content \{ max-width: 600px; height: 80vh; \}\ .modal-header \{ padding: 1rem 1.5rem; border-bottom: 1px solid #374151; flex-shrink: 0; \}\ #whats-playing-list, #request-iframe-wrapper \{ overflow-y: auto; padding: 1rem 1.5rem; \}\ #request-iframe-wrapper \{ padding: 0; flex-grow: 1; \}\ #request-iframe \{ width: 100%; height: 100%; border: none; \}\ .modal-list-item \{ padding-bottom: 0.75rem; margin-bottom: 0.75rem; border-bottom: 1px solid #374151; \}\ .modal-list-item:last-child \{ border-bottom: none; margin-bottom: 0; padding-bottom: 0;\}\ .modal-list-item[data-station-index]:hover \{ background-color: #374151; cursor: pointer; \}\ @keyframes dynamic-pause-background \{ 0% \{ background-position: 0% 50%; \} 50% \{ background-position: 100% 50%; \} 100% \{ background-position: 0% 50%; \} \}\ .paused-background \{ background: linear-gradient(270deg, #000000, #2c003e, #000000); background-size: 200% 200%; animation: dynamic-pause-background 20s ease infinite; \}\ ";\ \}\ \ // 5. Function to return the JavaScript for the player.\ function tracyradio_get_player_javascript() \{\ return "\ const rootEl = document.getElementById('tracyradio-player-container');\ if (!rootEl) return;\ \ rootEl.innerHTML = `\ \
\ \TracyRadio.com
\...
\\